#include "TPlaca.h"
#include "TMedio.h"
#include "TMux.h"
#include "TComando.h"
#include "TListaPlacaMux.h"
#include "straux.h"

#define CMD_SALIDA "QUIT"
#define CMD_CONNECT "CONNECT"
#define CMD_DISCONNECT "DISCONNECT"
#define CMD_RECV "RECV"
#define CMD_SEND "SEND"
#define CMD_HELP "HELP"

#define MAX_INPUT 256
#define MAIN_OK 0
#define MAIN_ERR_MEMORIA 1
#define MAIN_ERR_NOHAYCONECTADAS 2
#define MAIN_ERR_PLACANOENCONTRADA 3
#define MAIN_ERR_NOHAYMENSAJES 4
#define MAIN_ERR_NODESCONECTAPLACA 5
#define MAIN_ERR_PLACANOBORRADA 6
#define MAIN_ERR_PLACASIGUALES 7
#define MAIN_ERR_LISTA 8
#define MAIN_ERR_PLACAFUERARANGO 9
#define MAIN_ERR_PLACACREAR 10
#define MAIN_ERR_MUXCREAR 11
#define MAIN_ERR_PLACAYACONECTADA 12
#define MAIN_ERR_PLACACONECTAR 13
#define MAIN_ERR_PLACAYATIENEMUX 14
#define MAIN_ERR_MUXYATIENEPLACA 15
#define MAIN_ERR_PLACACONECTARMUX 16

/*#define __DEBUG__*/

#define MSG_ERR_CMDDESCONOCIDO "ERROR: Comando desconocido. Escriba 'help' para obtener ayuda.\n\n"
#define MSG_HELP " CONNECT <D>                      Conecta la placa de direccion <D> al medio\
                \n DISCONNECT <D>                   Desconecta del medio la placa de direccion <D>\
                \n SEND <O> <D> <P> <S/N> <MENSAJE> Envia <MENSAJE> desde <O> a <D> al puerto <P> \n \t\t\t\t  <S/N> afirma si es prioritario el mensaje\
                \n RECV <D> <P>                     Muestra el contenido del puerto <P> de la \n \t\t\t\t  placa <D>\
                \n HELP                             Muestra este texto\n\n"
#define MSG_BIENVENIDA "TP2: Listas, pilas y colas\nSimulador de Red de Computadoras v2.0\n\nEscriba 'help' para obtener ayuda.\n\n"

#define MSG_PLACA_ERR_RANGO "ERROR: Direccion de la placa fuera de rango.\n"
#define MSG_MEDIO_ERR_NOHAYCONECTADAS "ERROR: No hay placas conectadas al medio."

int ControlarPlacas (TListaPlacaMux* lista_placas, TElementoListaPlacaMux* elemento, TDireccion direccion){
    if (!TLPM_Vacia(lista_placas)) {
		TLPM_MoverCorriente(lista_placas, LS_PRIMERO);
		TLPM_ElementoCorriente(lista_placas, elemento);
		if (Placa_GetDireccion(elemento->placa) == direccion) {
			return MAIN_ERR_PLACASIGUALES;
		}

		while (!TLPM_MoverCorriente(lista_placas, LS_SIGUIENTE)) {
			TLPM_ElementoCorriente(lista_placas,elemento);
			if (Placa_GetDireccion(elemento->placa) == direccion){
				return MAIN_ERR_PLACASIGUALES;
			}
		}
	}
	return MAIN_OK;
}

void LiberarMemoria (TElementoListaPlacaMux** elemento, TPlaca** placa, TMux** mux){
    free (*elemento);
    free (*placa);
    free (*mux);
}


int ejecutar_connect(char* argumentos, TMedio* medio, TListaPlacaMux* lista_placas) {
	TDireccion direccion;
	TPlaca* placa;
	TMux* mux;
	int codigo_error;
	TElementoListaPlacaMux* elemento;

	#ifdef __DEBUG__
	printf("ejecutar_connect\n");
	#endif

	sscanf(argumentos, "%d", (int*)&direccion);

	#ifdef __DEBUG__
    printf("ejecutar_connect: direccion: %d.\n",direccion);
    #endif

	placa = (TPlaca*) malloc(sizeof(TPlaca));
	if (!placa) {
		return MAIN_ERR_MEMORIA;
	}

	mux = (TMux*) malloc(sizeof(TMux));
	if (!mux) {
		free(placa);
		return MAIN_ERR_MEMORIA;
	}

	elemento = (TElementoListaPlacaMux*) malloc(sizeof(TElementoListaPlacaMux));
	if (!elemento) {
		free(placa);
		free(mux);
		return MAIN_ERR_MEMORIA;
	}

	if (ControlarPlacas(lista_placas, elemento, direccion)){
        LiberarMemoria (&elemento,&placa,&mux);
        return MAIN_ERR_PLACASIGUALES;
    }

	codigo_error = Placa_Crear(placa, direccion);
	if (codigo_error) {
		LiberarMemoria (&elemento,&placa,&mux);
		switch (codigo_error) {
		case PLACA_ERR_RANGO:
			return MAIN_ERR_PLACAFUERARANGO;
		default:
			return MAIN_ERR_PLACACREAR;
		}
	}

    #ifdef __DEBUG__
	printf("ejecutar_connect: Placa creada.\n");
	#endif
	codigo_error = Mux_Crear(mux);
	#ifdef __DEBUG__
	printf("ejecutar_connect: Mux creada.\n");
	#endif
	if (codigo_error) {
		LiberarMemoria (&elemento,&placa,&mux);
		switch (codigo_error) {
			case MUX_OK:
				return MAIN_OK;
		case MUX_ERR_MEMORIA:
			return MAIN_ERR_MEMORIA;
		default:
			return MAIN_ERR_MUXCREAR;
		}
	}

	#ifdef __DEBUG__
	printf("ejecutar_connect: Placa creada.\n");
	#endif

	codigo_error = Placa_Conectar(placa, medio);
	if (codigo_error) {
		LiberarMemoria (&elemento,&placa,&mux);
		switch (codigo_error) {
			/*case PLACA_ERR_PLACACONECTADA:
				return MAIN_ERR_PLACAYACONECTADA;*/
			default:
				return MAIN_ERR_PLACACONECTAR;
		}
	}

	codigo_error = Placa_ConectarMux(placa, mux);
	if (codigo_error) {
		LiberarMemoria (&elemento,&placa,&mux);
		switch (codigo_error) {
			/*case PLACA_ERR_MUXYACONECTADO:
				return MAIN_ERR_PLACAYATIENEMUX;
			case PLACA_ERR_MUXYATIENEPLACA:
				return MAIN_ERR_MUXYATIENEPLACA;*/
			default:
				return MAIN_ERR_PLACACONECTARMUX;
		}
	}

	elemento->placa = placa;
	elemento->mux = mux;
	TLPM_InsertarElemento(lista_placas, elemento);

	#ifdef __DEBUG__
	printf("ejecutar_connect: Placa conectada.\n");
	printf("Contar Conectadas %d\n",(int)Medio_ContarConectadas(medio));
	printf ("Cantidad Placas Conectadas = %d\n",(int)Medio_ContarConectadas(medio));
	#endif
	free(elemento);
	return MAIN_OK;
}

int ejecutar_disconnect(char* argumentos, TMedio* medio, TListaPlacaMux* lista_placas) {
	TDireccion direccion;
	TElementoListaPlacaMux* elemento;
	char* dir[LONGITUD_MAXIMA];
	*dir = (char*) malloc(LONGITUD_MAXIMA);

	if (TLPM_Vacia(lista_placas)) {
		return MAIN_ERR_PLACANOENCONTRADA;
	}
	sscanf(argumentos, "%d", (int*)&direccion);

	elemento = (TElementoListaPlacaMux*) malloc (sizeof (TElementoListaPlacaMux));
	if (!elemento) {
		return MAIN_ERR_MEMORIA;
	}


  TLPM_MoverCorriente(lista_placas, 0);
  TLPM_ElementoCorriente(lista_placas, elemento);

	while (Placa_GetDireccion(elemento->placa) != direccion) {
		if (TLPM_MoverCorriente(lista_placas, 1)) {
			free(elemento);
			free(*dir);
			return MAIN_ERR_PLACANOENCONTRADA;
		}
		TLPM_ElementoCorriente(lista_placas,elemento);
	}

	if (Placa_DesconectarMux(elemento->placa)) {
		free(elemento);
    free(*dir);
    return PLACA_ERR_MUXNODESCONECTADO;
	}

	if (Placa_Desconectar(elemento->placa)) {
		free(elemento);
		free(*dir);
		return MAIN_ERR_NODESCONECTAPLACA;
	}

	if (TLPM_BorrarCorriente(lista_placas)) {
		free(elemento);
		free(*dir);
		return MAIN_ERR_PLACANOBORRADA;
	}

	free(elemento);
	free(*dir);
	return MAIN_OK;
}

int ejecutar_send(char* argumentos, TMedio* medio, TListaPlacaMux* lista_placas) {
	char s_mensaje[MAX_INPUT];
	int tam, direccion, prioritario;
	TElementoListaPlacaMux *elemento;
	TDireccion destino, origen;
	TPuerto puerto;
	char prioridad;
	TPlaca* placa;
	sscanf(argumentos, "%d %d %d %c %s", (int*)&origen, (int*)&destino, (int*)&puerto, (char*)&prioridad, (char*)s_mensaje);

	if (prioridad == 'S' || prioridad == 's') {
		prioritario = PRIORITARIO;
	}
	else {
		prioritario = NO_PRIORITARIO;
	}

	elemento = (TElementoListaPlacaMux*) malloc(sizeof(TElementoListaPlacaMux));
	if (!(elemento)) {
		return MAIN_ERR_MEMORIA;
	}

	placa = NULL;

	if (TLPM_Vacia(lista_placas)) {
		free(elemento);
		return MAIN_ERR_NOHAYCONECTADAS;
	}
	else {
		TLPM_MoverCorriente(lista_placas, LS_PRIMERO);
		TLPM_ElementoCorriente(lista_placas, elemento);

		while ((direccion = Placa_GetDireccion(elemento->placa)) != destino) {
			if (TLPM_MoverCorriente(lista_placas, LS_SIGUIENTE)) {
				free(elemento);
				return MAIN_ERR_PLACANOENCONTRADA;
			}

			#ifdef __DEBUG__
			printf("ejecutar_send:\n origen = %d\n destino = %d\n puerto = %d\n prioridad = %d\n s_mensaje = %s\n", origen, destino, puerto, prioridad, s_mensaje);
			#endif
			TLPM_ElementoCorriente(lista_placas,elemento);
		}
	}

    tam = strlen(s_mensaje);
	Mux_Enviar(elemento->mux, s_mensaje, tam, destino, puerto, prioritario);
	free(elemento);
	return 0;

}

int ejecutar_recv(char* argumentos, TMedio* medio, TListaPlacaMux* lista_placas) {
	int codigo_error = MAIN_OK, encontrado, problema_mux;
	TDireccion direccion;
	TPuerto puerto = 0;
	char* destino;
	TElementoListaPlacaMux* elemento;

	#ifdef __DEBUG__
	printf("ejecutar_recv:\n Entra.\n");
	#endif

	if (TLPM_Vacia(lista_placas)) {
		return MAIN_ERR_NOHAYMENSAJES;
	}

	sscanf(argumentos, "%d %d", (int*)&direccion, (int*)&puerto);

	elemento = (TElementoListaPlacaMux*) malloc(sizeof(TElementoListaPlacaMux));
	if (!elemento) {
		return MAIN_ERR_MEMORIA;
	}

	encontrado = FALSE;

	TLPM_MoverCorriente(lista_placas, 0);
	TLPM_ElementoCorriente(lista_placas, elemento);

	while (elemento && !encontrado) {
		if ((Placa_GetDireccion(elemento->placa) == direccion) && !encontrado) {
			destino = (char*) malloc(MAX_CARGA + 1);
			while (!(problema_mux = Mux_Leer(elemento->mux, puerto, destino))) {
				printf("[Placa %d - Puerto %d] %s\n", Placa_GetDireccion(elemento->placa), (int)puerto, (char*)destino);
			}

			encontrado = TRUE;
			free(destino);
		}

		TLPM_MoverCorriente(lista_placas, 1);
		TLPM_ElementoCorriente(lista_placas, elemento);
  }

	free(elemento);
	return codigo_error;
}

int ejecutar_hola(char comando[]) {
	char nombre[MAX_INPUT];
	sscanf(comando, "%s", nombre);
	printf("Hola, %s! :-)\n", nombre);
	return MAIN_OK;
}

void mensajes_disconnect(int salida) {
	switch (salida) {
		case MAIN_OK:
			printf("Placa desconectada correctamente.\n");
			break;
		case MAIN_ERR_MEMORIA:
			printf("Memoria insuficiente.\n");
			break;
		case MAIN_ERR_PLACANOENCONTRADA:
			printf("Error: no se encontro una placa con esa direccion.\n");
			break;
		default:
			printf("Error al desconectar la placa.\n");
			break;
	}
}

void mensajes_connect(int salida) {
	switch (salida) {
		case MAIN_OK:
			printf("Placa conectada al medio correctamente.\n");
			break;
		case MAIN_ERR_PLACASIGUALES:
			printf("Error: ya hay una placa conectada al medio con la direccion especificada.\n");
			break;
		case MAIN_ERR_MEMORIA:
			printf("Error: memoria insuficiente.\n");
			break;
		case MAIN_ERR_PLACAFUERARANGO:
			printf("Error: direccion fuera de rango.\n");
			break;
		case MAIN_ERR_PLACACREAR:
			printf("Error al crear la placa.\n");
			break;
		case MAIN_ERR_MUXCREAR:
			printf("Error al crear el multiplexor.\n");
			break;
		case MAIN_ERR_PLACAYACONECTADA:
			printf("Error: la placa ya esta conectada\n");
			break;
		case MAIN_ERR_PLACACONECTAR:
			printf("Error al intentar conectar la placa.\n");
			break;
		case MAIN_ERR_PLACAYATIENEMUX:
			printf("Error: la placa ya tiene conectado un multiplexor.\n");
			break;
		case MAIN_ERR_MUXYATIENEPLACA:
			printf("Error: el multiplexor ya tiene conectada una placa.\n");
			break;
		case MAIN_ERR_PLACACONECTARMUX:
			printf("Error al intentar conectar el multiplexor a la placa.\n");
			break;
		default:
			printf("Error desconocido.\n");
			break;
	}
}

void interpretar_comando(char* operacion, char* argumentos, TMedio* medio, TListaPlacaMux* lista_placas) {
	int salida;

	if (!strcasecmp(operacion, "HOLA")) {
		ejecutar_hola(argumentos);
	}
	else if (!strcasecmp(operacion, CMD_SEND)) {
		salida = ejecutar_send(argumentos, medio, lista_placas);
	}
	else if (!strcasecmp(operacion, CMD_RECV)) {
		salida = ejecutar_recv(argumentos, medio, lista_placas);
	}
	else if (!strcasecmp(operacion, CMD_CONNECT)) {
		salida = ejecutar_connect(argumentos, medio, lista_placas);
		mensajes_connect(salida);
	}
	else if (!strcasecmp(operacion, CMD_DISCONNECT)) {
		salida = ejecutar_disconnect(argumentos, medio, lista_placas);
		mensajes_disconnect(salida);
	}
	else if (!strcasecmp(operacion, CMD_HELP)) {
		printf(MSG_HELP);
	}
	else if (!strcasecmp(operacion, CMD_SALIDA)) {
		/* salida */
	}
	else {
		printf(MSG_ERR_CMDDESCONOCIDO);
	}
}

int mostrar_prompt(TMedio* medio, TListaPlacaMux* lista_placas) {
	char* comando;
	char* operacion;
	char* argumentos;

	comando = malloc(MAX_INPUT);
	if (!comando) {
		return MAIN_ERR_MEMORIA;
	}

	operacion = malloc(MAX_INPUT);
	if (!operacion) {
		free(comando);
		return MAIN_ERR_MEMORIA;
	}

	do {
		printf("> ");
		fgets(comando, MAX_INPUT + 1, stdin);
		sscanf(comando, "%s", operacion);
		fflush(stdin);
		argumentos = strchr(comando, ' ') + sizeof(char);
		interpretar_comando(operacion, argumentos, medio, lista_placas);
	} while (strcasecmp(operacion, CMD_SALIDA));

	free(comando);
	free(operacion);
	return MAIN_OK;
}

int mostrar_consola() {
	TMedio medio;
	TListaPlacaMux lista_placas;
	int tam_elemento, error;

	tam_elemento = sizeof(TElementoListaPlacaMux);
	TLPM_Crear(&lista_placas, tam_elemento);
	Medio_Crear(&medio);

	error = mostrar_prompt(&medio, &lista_placas);
	if (error) {
		/* TODO: manejar salida */
	}

	TLPM_Destruir(&lista_placas);
	TLPM_Vaciar(&lista_placas);
	Medio_Destruir(&medio);
	return MAIN_OK;
}

int main(int argc, char* argv[]) {
	printf(MSG_BIENVENIDA);
	mostrar_consola();
	return MAIN_OK;
}
